package com.changgou.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import pojo.SkuInfo;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {
    //使用spring-data-elasticsearch技术进行全文检索
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;


    @Override
    public Map searchQuery(Map<String, String> searchMap) {
        Map resultMap = new HashMap();
        if (searchMap != null) {
            //2.查询条件构建类
            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
            //3.BoolQueryBuilder是QueryBuilder接口实现类
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

            if (searchMap.get("keywords") != null) {
            /*
            添加查询条件
            must是必须的,matchQuery表示模糊匹配查询,参数1表示映射名称
            参数2表示映射值
             */

                boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
            }


            //执行品牌过滤
            if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
                boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
            }

            //规格过滤条件,spec_屏幕尺寸
            for (String key : searchMap.keySet()) {
                if (key.startsWith("spec_")) {//表示是规格
                    String value = searchMap.get(key);//获取规格值
                    //明明需要凭借specMap.屏幕尺寸.keyword
                    boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));//key.substring(5)====>"spec_"
                }
            }
            //2.4 价格区间过滤条件
            if (StringUtils.isNotEmpty(searchMap.get("price"))) {//有价格关键字
                String[] prices = searchMap.get("price").split("-");
                if (prices.length == 2) {//3000已下的价格
                    boolQuery.filter(QueryBuilders.rangeQuery("price").lt(prices[1]));//小于第二个数字

                }
                //否则大于第一个数字
                boolQuery.filter(QueryBuilders.rangeQuery("price").gt(prices[0]));
            }
            //4.把条件对象给条件构建器
            queryBuilder.withQuery(boolQuery);
        /*
        3.1 品牌聚合查询
        addAggregation方法表示添加聚合,(分组)
        terms表示从当前文档中匹配指定的表示
        AggregationBuilders.terms(skuBrand)可以认为是当前聚合中key
        field("brandName")表示指定具体哪个域进行分组
        意思相当于sql:select * from 表 group by brandName
         */
            String skuBrand = "skuBrand";//其实可以作为一个标识
            queryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));

            //3.2 规格聚合查询
            String skuSpec = "skuSpec";
            queryBuilder.addAggregation((AggregationBuilders.terms(skuSpec).field("spec.keyword")));

            //分页查询
            String pageNum = searchMap.get("pageNum");
            if (pageNum == null) {
                pageNum = "1";
            }
            String size = searchMap.get("size");
            if (size == null) {
                size = "30";
            }
            /*
            开始添加分页
            of()参数1表示当前页码的索引,从0开始
                参数2表示每页显示的记录数
             */
            Pageable pageable = PageRequest.of(Integer.parseInt(pageNum) - 1, Integer.parseInt(size));
            queryBuilder.withPageable(pageable);


            /*
            3.4排序查询
            sortFiled表示排序字段域,sortRule表示排序规则,ASC和DESC
             */
            if (StringUtils.isNotEmpty(searchMap.get("sortFiled"))) {
                if ("ASC".equals(searchMap.get("sortRule"))) {
                    queryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortFiled")).order(SortOrder.ASC));
                } else {
                    queryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortFiled")).order(SortOrder.DESC));
                }
            }

            //3.5高亮显示
            HighlightBuilder.Field fields = new HighlightBuilder
                    .Field("name")//作用域
                    .preTags("<font style='color:red'>")//前置标签
                    .postTags("</font>");//后置标签
            queryBuilder.withHighlightFields(fields);

        /*
        1.执行查询
        参数1表示查询条件对象
        参数2表示映射的实体类
        参数3表示搜索查询结果映射
        返回值就是查询的结果
         */
            AggregatedPage<SkuInfo> skuInfos = elasticsearchTemplate.queryForPage(queryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                /**
                 *
                 * @param searchResponse 搜索相应对象
                 * @param aClass 类
                 * @param pageable 分页组件类,没有执行分页的话,每页默认是10条
                 * @param <T>
                 * @return
                 */

                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                    List list = new ArrayList();
                    //1.获取命中结果集,SearchHits是 一个集合对象
                    SearchHits hits = searchResponse.getHits();
                    for (SearchHit hit : hits) {
                        //获取结果json字符串,表示索引库中每一行数据
                        String sourceAsString = hit.getSourceAsString();
                        //要把字符串转化为实体类
                        SkuInfo skuInfo = JSON.parseObject(sourceAsString, SkuInfo.class);
                        //高亮设置
                        Map<String, HighlightField> fieldMap = hit.getHighlightFields();
                        if (fieldMap != null && fieldMap.size() > 0) {
                            skuInfo.setName(fieldMap.get("name").getFragments()[0].toString());
                        }
                        //封装转化的每个时提到一个list集合中
                        list.add(skuInfo);
                    }
                    /**
                     * 参数1表示封装的list集合
                     * 参数2表示分页组件
                     * 参数3表示命中结果总记录数
                     * 参数4表示查询相应的聚合
                     */

                    return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
                }
            });
            //封装数据
            resultMap.put("total", skuInfos.getTotalElements());//总记录数
            resultMap.put("rows", skuInfos.getContent());//总页数
            resultMap.put("totalPages", skuInfos.getTotalPages());//数据列表
            //聚合品牌的封装
            StringTerms stringTerms = (StringTerms) skuInfos.getAggregation(skuBrand);
            //流运算
            List<String> list = stringTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("brandList", list);

            //获取规格聚合查询结果集并且进行封装
            StringTerms specTerms = (StringTerms) skuInfos.getAggregation(skuSpec);
            List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("specList", this.specFormate(specList));

            //封装页码
            resultMap.put("pageNum", pageNum);

            return resultMap;
        }
        return null;
    }


    //转化规格格式
    public Map<String, Set<String>> specFormate(List<String> specList){
        //1.定义一个map集合用来封装转化之后的规格
        Map<String,Set<String>> resultMap = new HashMap<>();
        if (specList!=null&&specList.size()>0){
            for (String specJsonString : specList) {//spec="{'颜色': '蓝色', '版本': '6GB+128GB'}"
            //把json字符串转化成map
            /*
            {
                颜色:黑色
                版本:6GB+128GB
            }
             */
                Map<String,String> specMap = JSON.parseObject(specJsonString, Map.class);
                for (String specKey : specMap.keySet()) {//specKey表示的是规格名称
                    Set<String> specSet = resultMap.get(specKey);
                    if (specSet == null){
                        specSet = new HashSet<String>();
                    }
                    specSet.add(specMap.get(specKey));//set中封装的都是规格选项

                    //将set封装到map中
                    resultMap.put(specKey,specSet);
                }
            }
        }
        return resultMap;
    }



}
